#include <iostream>
#include <fstream>
#include "Constants.hpp"
#include "ObjectManager.hpp"
#include "Object.hpp"

#define FOREACH(it, data) \
    for (__typeof((data).begin()) it = (data).begin(); it != (data).end(); ++it)

ObjectManager::ObjectManager() { // {{{
    dt = DEFAULT_DT;
    objs.clear();
    elapsed_time = 0;
} // }}}

ObjectManager::~ObjectManager() { // {{{
} // }}}

void ObjectManager::initObjects(const char* conf_file) { // {{{
    // TODO: support comments in the file
    std::cout << "Reading config from `" << conf_file << "'..." << std::endl;

    std::ifstream in_file(conf_file, std::ifstream::in);
    char label[10];
    bool is_star;
    double x, y;
    double vx, vy;
    double m;

    in_file >> label >> is_star >> x >> y >> vx >> vy >> m;
    while (in_file) {
        objs.push_back(Object(label, is_star, x, y, vx, vy, m));
        in_file >> label >> is_star >> x >> y >> vx >> vy >> m;
    }
} // }}}

void ObjectManager::step() { // {{{
    // TODO: collisions: refactor me... please!
    FOREACH(obj, objs) {
        Vec2d F(0,0);
        ObjContainer::iterator collision = objs.end(); // TODO: do we really want collisions? without them this could run in O(N)
        FOREACH(obj2, objs)
            if (obj2 != obj) {
                if (obj->distanceTo(*obj2) < 1e7) {
                    collision = obj2;
                    Collide(obj, obj2);
                }
                else
                    F += ((obj2->getPos() - obj->getPos()) * G * obj->getM() * obj2->getM()) / pow(obj->distanceTo(*obj2), 3);
            }
        if (collision != objs.end()) // we can't delete it right there -^ because it will invalidate pointers
            objs.erase(collision);

        F *= ObjectManager__SOME_SCALING_FACTOR_USED_WHEN_CALCULATING_GRAVITATIONAL_FORCE;

        Vec2d a = F / obj->getM();
        Vec2d dv = a * dt;
        obj->changeVelBy(dv);

        Vec2d dist = obj->getVel() * dt;
        obj->MoveBy(dist);
    }
    elapsed_time += dt/ObjectManager__SOME_SCALING_FACTOR_USED_WHEN_CALCULATING_GRAVITATIONAL_FORCE;
    
    COMCenteringCorrection();
} // }}}

ObjContainer* ObjectManager::getAllObjects() { // {{{
    return &objs;
} // }}}

void ObjectManager::COMCenteringCorrection() { // {{{
    // 1st find COM
    Vec2d sum(0,0);
    double sumM = 0;
    FOREACH(obj, objs) {
        sum += obj->getPos() * obj->getM();
        sumM += obj->getM();
    }
    Vec2d COM = sum / sumM;
    // now adjust coordinates of things
    FOREACH(obj, objs)
        obj->MoveBy(COM * -1);
//    std::cout << "COMCenteringCorrection(): " << COM.x << ", " << COM.y << std::endl;
} // }}}

void ObjectManager::Collide(ObjContainer::iterator obj1, ObjContainer::iterator obj2) { // {{{
    std::cout << "Boom!" << std::endl;
    obj1->setM(obj1->getM() + obj2->getM());
    obj1->setVel((obj1->getVel() * obj1->getM() + obj2->getVel() * obj2->getM()) / (obj1->getM() + obj2->getM()));
} // }}}

void ObjectManager::changePrecisionBy(float r) { // {{{
    dt *= r;
} // }}}

float ObjectManager::getTimeYears() { // {{{
    return elapsed_time / SECS_IN_ONE_YEAR;
} // }}}
